/*
 Copyright (C) 2007 - Michael Opitz
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "parser.h"
#include "gmail.h"
#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>


/** 
 * gmail_parser_doit:
 * 
 * @doc is a pointer to a xmlDoc.
 * @list is a pointer to a GList to which the infos should be appened. The list
 * can be NULL.
 * 
 * parses all <entry> nodes with a XPATH-Expression and returns the 
 * parsed GList with dynamic allocated MessageInfo structs
 *
 * Returns: a GList with MessageInfo structures.
 */
GList* 
gmail_parser_doit (xmlDocPtr doc, GList *list);
/** 
 * gmail_parser_author:
 * 
 * @doc: is a pointer to a xmlDoc
 * @ptr: is a pointer to the node.
 * @from_mail: is a pointer to a pointer which will point to the address from
 * the sender.
 * @from_name: is a pointer to a pointer which will point to the name 
 * of the sender.
 *
 * Parses the author-field in the gmail-atom-feed.
 *
 */
void 
gmail_parser_author (xmlDocPtr doc, xmlNodePtr ptr, char **from_mail, 
		     char **from_name);
/** 
 * gmail_parse_do_child:
 * 
 * @doc: a pointer to a xmlDoc
 * @ptr: a pointer to a xmlNode
 * @list: a pointer to the GList to which the MessageInfo struct should be
 * appened.
 *
 * This function parses a sub-node and creates a MessageInfo struct and appends
 * this allocated struct to the GList.
 * 
 * Returns: a GList is returned.
 */
GList* 
gmail_parse_do_child (xmlDocPtr doc, xmlNodePtr ptr, GList *list);

void 
gmail_parse_cleanup ()
{
	xmlCleanupParser ();
}


/*
 * just for debugging purposes...
 */
static void
print_element_names (xmlNode * a_node)
{
	xmlNode *cur_node = NULL;

	for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
		if (cur_node->type == XML_ELEMENT_NODE) {
			printf("node type: Element, name: %s\n", 
			       cur_node->name);
		}
		print_element_names(cur_node->xmlChildrenNode);
	}
}

/*
 * parsing the list!
 */
GList* 
gmail_parse_it (const char *parse)
{
	/*
	 * return NULL if this isn't a valid xml-feed or if not the whole feed
	 * was received.
	 */
	char *start = strstr (parse, "<?xml");
	if (start == NULL) return NULL;
	char *end = strstr (parse, "</feed>");
	if (end == NULL) return NULL;
	end = strchr (end, '>');

	/* terminate the string correctly */
	int length = end - start + 1;
	start[length] = '\0';

	/* the xml-parser should only initialized once, because otherwise we get
	 * some strange memory leaks.
	 */
	static int init_parser = 0;
	if (init_parser == 0) {
		xmlInitParser ();
		init_parser = 1;
	}

	/* parsing the xml to a dom node */
	xmlDocPtr doc = xmlParseMemory ((const char*)start, strlen (start) + 1);
	if (doc == NULL) {
		fprintf (stderr, "error, couldn't parse feed");
		return NULL;
	}
	/* parse the dom-node into the internal MessageInfo structure and return
	 * a list of MessageInfo structures
	 */
	GList *list = NULL;
	if (doc) {
		list = gmail_parser_doit (doc, list);
		xmlFreeDoc (doc);
	}

	return list;
}

/** 
 * gmail_alloc_char:
 * 
 * This is just a little helper function which returns an empty '\0' terminated
 * string.
 *
 * Returns: a string.
 */
char* 
gmail_alloc_char ()
{
	char *tmp = (char*) malloc (sizeof(char));
	tmp = '\0';
	return tmp;
}

/*
 * Parse the author-field in the gmail-atom-feed
 */
void gmail_parser_author (xmlDocPtr doc, xmlNodePtr ptr, char **from_mail, 
			  char **from_name)
{
	/* the author node actually contains the name node and the email-node
	 * which we want.
	 */
	for (; ptr; ptr = ptr->next) {
		if (ptr->type == XML_ELEMENT_NODE) {
			if (ptr->xmlChildrenNode && 
			    !strcmp ((const char*) ptr->name, "name")) {
				xmlChar *tmp = xmlNodeListGetString (doc, 
							ptr->xmlChildrenNode,
							1);
				(*from_name) = g_strdup ((const char*) tmp);
				xmlFree (tmp); 
			} else if (ptr->xmlChildrenNode && 
				   !strcmp((const char*) ptr->name, "email")) {
				xmlChar *tmp = xmlNodeListGetString (doc, 
							ptr->xmlChildrenNode,
							1);
				(*from_mail) = g_strdup ((const char*)tmp);
				xmlFree (tmp);
			}
		}
	}
}

GList* 
gmail_parse_do_child (xmlDocPtr doc, xmlNodePtr ptr, GList *list) 
{
	if (ptr == NULL) {
		return list;
	}
	char *from_mail = NULL;
	char *from_name = NULL;
	char *snipped = NULL;
	char *url = NULL;
	char *subject = NULL;
	xmlNodePtr child;
	/* we are walking through each child and converting the corresponding nodes
	 * into the corresponding MessageInfo fields 
	 */
	for (child = ptr; child; child = child->next) {
		if (child->type == XML_ELEMENT_NODE) {
			if (!strcmp ((const char*) child->name, "summary")) {
				xmlChar *tmp = xmlNodeListGetString (doc, 
							child->xmlChildrenNode,
							1);
				snipped = g_strdup ((const char*) tmp);
				xmlFree (tmp);
			} else if (!strcmp ((const char*) child->name, 
							  "title")) {
				xmlChar *tmp = xmlNodeListGetString (doc, 
							child->xmlChildrenNode,
							1);
				subject = g_strdup ((const char*) tmp);
				xmlFree(tmp);
			} else if (!strcmp ((const char*) child->name, 
							  "link")) {
				const char *tmp = (const char*) xmlGetProp (
						child, BAD_CAST "href");
				url = g_strdup (tmp);
				free ((void*) tmp);
			} else if (!strcmp((const char*)child->name, "author")) {
				gmail_parser_author (doc, child->xmlChildrenNode,
						     &from_mail, &from_name);
			}
		}
	}
	MessageInfo *info = (MessageInfo*) malloc (sizeof (MessageInfo));
	{
		/* Actually, each of our fields must be filled with either the
		 * parsed node info or with an empty string 
		 */
		info->from_mail = (from_mail) ? from_mail : gmail_alloc_char ();
		info->from_name = (from_name) ? from_name : gmail_alloc_char ();
		info->subject = (subject) ? subject : gmail_alloc_char ();
		info->snipped = (snipped) ? snipped : gmail_alloc_char ();
		info->url = (url) ? url : gmail_alloc_char ();
	}
	list = g_list_append (list, (void*) info);
	return list;
}


/*
 * parses all <entry> nodes with a XPATH-Expression and returns the 
 * parsed GList with dynamic allocated MessageInfo structs
 */
GList* gmail_parser_doit (xmlDocPtr doc, GList *list)
{
	/* before we start parsing we need a context */
	xmlXPathContextPtr ctx = (xmlXPathContextPtr) xmlXPathNewContext (doc);
	if (ctx == NULL) {
		g_error ("Error, couldn't create xpath-ctx");
		return list;
	}
	/* we also should register the atom namespace or else we'll run into
	 * some strange errors
	 */
	if (xmlXPathRegisterNs (ctx, BAD_CAST "atom", 
	   BAD_CAST "http://purl.org/atom/ns#") != 0) {
		fprintf (stderr, "Error, couldn't register namespace");
		return list;
	}
	/* we are searching for <entry> nodes */
	xmlChar *tmp = (xmlChar*)"//atom:entry";
	xmlXPathObjectPtr obj = 
		xmlXPathEvalExpression (tmp, ctx);
	if(obj == NULL) {
		g_error("Error, xpath-expression failed");
		xmlXPathFreeContext (ctx);
		return list;
	}

	/* we are walking through the result and calling gmail_parse_do_child
	 * for each <entry> node which will convert the dom-structure for us to
	 * MessageInfo structs
	 */
	xmlNodeSetPtr nodes = obj->nodesetval;
	int size = nodes->nodeNr;
	int i;
	for (i = 0; i < size; ++i) {
		g_assert (nodes->nodeTab[i]);
		if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
			xmlNodePtr cur = nodes->nodeTab[i];
			xmlNodePtr child = cur->xmlChildrenNode;
			list = gmail_parse_do_child (doc, child, list);
		}
	}
	
	/* cleanup */
	xmlXPathFreeObject (obj);
	xmlXPathFreeContext (ctx);
	return list;
}

